ML is fun!(part3深度学习和卷积神经网络)

英文出处:https://medium.com/@ageitgey/machine-learning-is-fun-part-3-deep-learning-and-convolutional-neural-networks-f40359318721#.ez3em55bo

你是否已经厌倦了阅读无休止的深度学习的文章,却不知道其真正含义?让我们改变这种窘境!。

这一次,我们将学习如何编写程序,运用深度学习去识别图像中的对象。换句话说,我们将向你解释Google Photos是如何通过照片本身来找到正是你需要照片,这种黑魔法的神奇:

Google now lets you search your own photos by description—even if they aren’t tagged! How does this work??

正如Part1和Part2所述,本文是为了那些对机器学习充满好奇,而又无从下手的人准备的。

深度学习的目标识别

你可能之前已经看过著名的xkcd漫画

xkcd #1425

一个3岁的小孩可以识别出照片上的一只鸟,但是怎样让计算机识别目标困恼了科学家50年,即便是最优秀的计算机领域的专家。

在过去的几年里,我们终于找到一种很好的方法来识别目标物体,即深度卷积神经网络。这提起来像是威廉吉布森小说里一个词一个词组合而成,但是其思想完全可以有单独的词来理解。

让我写一个程序来识别这些可爱的鸟吧!

从简单地开始

在我们学习如何识别鸟类图片之前,我们先学习识别一些更简单的——比如手写的数字“8”。

Part2,我们学习神经网络是把许许多多简单的神经元连接在一起,能够解决很多复杂度的问题。在卧室个数,面积,和所在区域这些影响因素的基础上,我们创建了一个小的神经网络来估算房屋的价格:

enter description here

我们都知道机器学习的精髓,就是相同的泛型算法可以重复使用不同的数据来解决不同的问题。所以我们可以修改同样的神经网络来识别手写文本。但是为了让这项工作更简单,我们就只是识别一个字符——数字”8“。

当你有数据的时候,机器学习才能有效——最好是大量的数据。所以我们需要很多很多的手写体”8“,才能开始训练。幸运的是,研究者创建了MNIST数据库,专门是为手写数字准备的数据集。MNIST提供60,000张手写体图片,每一张大小18x18。这里截出一些”8“的图片:

Some 8s from the MNIST data set

其实想一想,一切都是数字

在Part2中,我们仅采用3个数字作为神经网络的输入(3间卧室,2000平方英尺等)。但是现在我们想要用同样的神经网络来处理图片。问题来了,怎样把图片塞进神经网络里面而不是简单的几个数字?

答案是难以置信的简单。神经网络以数字作为输入。对计算机而言,一张图片其实是数字组成的网格,这些数字代表每个像素是有多黑:

enter description here

一张图片如何输入到我们的神经网络,只要简单的把18x18的图片看成324个数字的数组:

enter description here

为了处理这324个输入,我们只要扩大我们的神经网络到324个输入节点:

enter description here

值得注意的是,我们的神经网络也有两个输出(而不是一个)。第一个输出表述预测是图像”8“的可能性,第二个表示预测不是图像”8“的可能性。通过分开不同物体类型的输出,我们也可以使用神经网络把物体分类到不同组别。

我们的神经网络已经比上次大得多(324的输入而不是3的!)。不用担心,现代计算机可以处理几百个节点的神经网络。这甚至能在你的手机上运行。

剩下的工作就是用”8“的图片和不是”8“的图片来训练你的神经网络。当你输入一张”8“的图片,我们会告诉神经网络这张是”8“的可能性是100%,不是”8“的可能性是0%。反之,亦然。

这里是一些我们用于训练的数据:

Mmm… sweet, sweet training data

在现代笔记本上,我们可以在几分钟内训练这种神经网络。完成后,我们可以用这个神经网络来识别图像”8“,有相当高的精确度。欢迎来到图像识别的世界!(可追述到1980年)。

致命陷阱

简单的输入像素到神经网络中,真的可以简历图片识别系统!机器学习多么神奇!….对吗?

当然不是那么简单。

首先,好消息是,我们的图像”8“识别模型可以工作的很好,对于那些”8“在图片中央的:

enter description here

但是,现实往往不是这样:

当字母不在图片中央的时候,我们的”8“识别模型总是失败。只要稍微一点位置变化就毁了一切:

enter description here

这是因为我们的神经网络模型只学习了中间”8“的模式。非中间”8“的情况,它完全不懂。它只能确定一种模式。

这在现实世界并不是很有用。现实世界的问题从未清晰简单。所以我们需要解决”8“不在中央的情况下,怎么样使得我们的神经网络很好的工作。

暴力法#1:滑动窗口搜索

我们已经创建了一个比较好的程序用于需找图片中央的”8“。如果我们以较小范围扫描整个图片,这个小范围叫做窗口,每次一个窗口,直到找到为止:

enter description here

这种方法我们称之为滑动窗口,比较蛮力的方法。在有一些限制的情况下,它工作的很好,但不是很有效率。你不得不一遍又一遍检测相同的图像,去寻找不同的尺寸。我们可以做得比这个更好!

暴力法#2:更多的数据和深度神经网络

当我们训练我的的网络模型时,我们只是显示了在图片中央的“8”。那如果我们用更多的数据来训练呢?包括不同大小、不同位置的“8”,又该如何?

我们甚至不需要收集新的训练数据。我们可以写一个脚本用于生成新的图片。“8”在这些图片的不同的位置:

We created Synthetic Training Data by creating different versions of the training images we already had. This is a very useful technique!

通过这个技术,我们可以轻而易举创造出无限多个的训练数据。

更多的数据使得我们的神经网络要解决的问题更加复杂,但是,我们可以让我们的网络模型变得更大,从而能够学习更多复杂的模式。

为了使我们的网络模型更强大,我们只是把一层一层的节点连接起来:

enter description here

我们把这个,称之为”深度神经网络“。因为它较之传统的神经网络,有更多的层。

这个想法自1960年代以来,一直存在。直到最近,训练一个大型的神经网络速度太慢,但是非常有用。但是,一旦我们想出怎样使用3D图形显卡(被用作矩阵乘法非常快),而不是平常的计算机处理器,那么大型的神经网络工作就变得非常实用。事实上,NVIDIA GeForce GTX 1080视频卡用作神经网络的训练,速度快得令人难以置信:

enter description here

即使我们可以使我们的神经网络变得很大,并用3D图形卡快速的训练它,它依然不能一路无阻的给我们一个解决方案。我们需要更加聪明,在用神经网络处理图像的时候。

仔细想一想。分开训练用于识别”8“在图片顶部和在图片底部的两个神经网络,是没有意义的。

应该有一些方法使得神经网络更加聪明,不需要额外的训练,就知道”8“在图片中的任何位置其实是一样的。幸运的是…答案就在这里!

解决方案就是卷积

以人类的视角,你直观地知道一张图片的层次或者概念结构。思考下面这张图片:

Gratuitous picture of my son

  • 地上覆盖着青草和水泥
  • 有一个小孩
  • 小孩坐在木马上
  • 木马放在青草上

重要的是,无论小孩坐在什么上面,我们都可以轻而易举地识别出小孩。我们不需要因为小孩坐在不同的表面上而去重新学习。

但现在,我们的神经网络做不到这样。它认为“8”位于图片不同的部分是完全不同的事情。它完全不理解目标物体在图片上移动并没有发生实质的变化。这就意味着我们的神经网络必须重新学习,在每个位置上识别目标物体。这个很糟糕!

我们需要让我们的神经网络明白平移不变性——“8”就是“8”,无论在图片的什么位置显示。

想要完成这个目的,我们使用一个过程,称之为卷积。卷积的概念一部分是受计算机科学启发,一部分是生物学引导(疯狂的科学家用奇怪的探针刺激猫的大脑,分析猫咪是如何处理图像的)。

卷积如何工作

无论目标物体出现在图像中哪个位置,我们利用它本身的特征做更多聪明的事情。而不是把整张图片作为神经网络的输入。

下面一步一步的展示工作流程——

Step 1:分割图像成重叠的图像块

类似于上面介绍的滑动窗口搜索,在原图上我们每次滑动一次窗口,并保存每个结果作为独立的、小的图像块:

enter description here

我们把原图分割成了77个同样大小的图像块。

Step 2:把每个小图像块输入小型神经网络

之前,我们把一张图片输入到神经网络中,看图片是不是手写体“8”。这里我们将做同样的事情,仅针对每个独立图像块:

Repeat this 77 times, once for each tile.

好吧,这里有一个大的迷惑点:在同一张原图上,我们将为每个单独的图像块,保持神经网络权值不变。换句话说,我们会平等对待每个单独的图像块。如果有什么感兴趣的东西在任何一个图像块上,我们将会标记这个图像块。

Step 3:保存每个图像块的结果到新数组

我们不想失去对原图像块的控制。所以我们保存的是处理每个图像块后的结果,按照原图分布保存。就像下面这样:

enter description here

换句话说,我们从一张大尺寸图像入手,以小型数组结尾。其中这个数组记录着原图中哪些部分是我们最感兴趣的。

Step 4: 减采样

Step 3的结果是一个数组,映射出原图中哪些部分是最感兴趣的。但这个数组还是太大了:

enter description here

为了降低数组的大小,我们使用一种算法减采样,称之为最大池化。这听起来非常有趣,但实际并不是!

我们会以2x2的方针扫描数组,并拿出最大的哪个数字:

enter description here

这个想法就是,我们可以在由4个输入图像块组成的2x2方阵中找到一些感兴趣的东西,我们就拿出这些感兴趣的项。这在降低我们数组大小的同时,保持了最重要的项。

Final step:做预测

做到这里,我们已经把一个巨大的图像缩小成了一个相当小的数组。

这个数组只是一堆数字,所以我们可以使用这个小数组作为下一层神经网络的输入。最后一层神经网络将决定这个图像是不是匹配。把这一步与卷积过程区分开来,我们称其为“全连接”。

所以从开始到完成,我们总共5个步骤看起来就像这样:

enter description here

添加更多的步骤

我们的图像处理过程就是一系列步骤:卷积,最大池化,和全连接。

当处理现实世界的问题的时候,这些步骤可以组合堆叠许多次。只要你想,你可以有2层、3层,甚至10个卷积层。你也可以做池化,在任何你想缩小数据大小的地方。

基本思想就是以一张大尺寸图像开始,继续一步一步的降维,直到你有一个单一的结果。卷积层越多,你的神经网络所能学习去识别的特越复杂。

例如,第一层卷积可以学习识别尖锐的边缘,第二层卷积可以使用尖锐边缘特征识别出喙,第三层卷积可以使用喙特征识别出整个鸟,等等。

这是一个更加现实的深度卷积神经网络(像你找到的某篇论文),如这样:

enter description here

在这种情况下,以一个224x224大小的图像开始,应用卷积和池化分别两次,接着应用卷积3次,最后应用一次池化和两次全连接。最终的结果就是爱图像被分类到1000个类别中的一种!

构建正确的神经网络

那么,你如何知道哪些步骤,你需要结合起来,使你的图像分类的更好?

说实话,要回答这个问题,你必须做大量的实验和测试。你可能需要训练100个神经网络,然后才能找到你所解决问题的最佳结构和参数。

机器学习涉及到大量的实验和错误!

建立我们鸟类的分类器

现在我们完全可以写一个程序,用来决定一张图片是不是鸟。

一如既往,我们需要一些数据开始。免费的CIFAR10数据集包含6,000张鸟类图片和52,000不是鸟类的图片。但是要获得更多的数据,我们可以加入加州理工学院的 Birds-200–2011的数据集,这里面有另外12,000张鸟类图片。

这里从我们的数据集截取一些鸟类图片:

enter description here

这里有53,000张非鸟类图片:

enter description here

这个数据集可以工作的很好,但是72,000张低分辨率图片集对于现实世界来说还是相当小的。如果你想要Google级别的性能,你需要百万数量的图片。在机器学习中,拥有更多的数据对于拥有算法更好的性能至关重要。现在你知道为什么Google这么乐意给你提供没有上限照片存储空间。他们想要你的甜蜜呀,甜蜜的数据!

为了建立我们的分类器,我们使用TFLearn。TFLearn是Google TensorFlow深度学习的开源库,已经有了很简易的API。它使得建立卷积神经网络变得很简单,写几行代码就可以定义我们的网络层。

下面是定义和训练网络模型的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# -*- coding: utf-8 -*-

"""
Based on the tflearn example located here:
https://github.com/tflearn/tflearn/blob/master/examples/images/convnet_cifar10.py
"""
from __future__ import division, print_function, absolute_import

# Import tflearn and some helpers
import tflearn
from tflearn.data_utils import shuffle
from tflearn.layers.core import input_data, dropout, fully_connected
from tflearn.layers.conv import conv_2d, max_pool_2d
from tflearn.layers.estimator import regression
from tflearn.data_preprocessing import ImagePreprocessing
from tflearn.data_augmentation import ImageAugmentation
import pickle

# Load the data set
X, Y, X_test, Y_test = pickle.load(open("full_dataset.pkl", "rb"))

# Shuffle the data
X, Y = shuffle(X, Y)

# Make sure the data is normalized
img_prep = ImagePreprocessing()
img_prep.add_featurewise_zero_center()
img_prep.add_featurewise_stdnorm()

# Create extra synthetic training data by flipping, rotating and blurring the
# images on our data set.
img_aug = ImageAugmentation()
img_aug.add_random_flip_leftright()
img_aug.add_random_rotation(max_angle=25.)
img_aug.add_random_blur(sigma_max=3.)

# Define our network architecture:

# Input is a 32x32 image with 3 color channels (red, green and blue)
network = input_data(shape=[None, 32, 32, 3],
data_preprocessing=img_prep,
data_augmentation=img_aug)

# Step 1: Convolution
network = conv_2d(network, 32, 3, activation='relu')

# Step 2: Max pooling
network = max_pool_2d(network, 2)

# Step 3: Convolution again
network = conv_2d(network, 64, 3, activation='relu')

# Step 4: Convolution yet again
network = conv_2d(network, 64, 3, activation='relu')

# Step 5: Max pooling again
network = max_pool_2d(network, 2)

# Step 6: Fully-connected 512 node neural network
network = fully_connected(network, 512, activation='relu')

# Step 7: Dropout - throw away some data randomly during training to prevent over-fitting
network = dropout(network, 0.5)

# Step 8: Fully-connected neural network with two outputs (0=isn't a bird, 1=is a bird) to make the final prediction
network = fully_connected(network, 2, activation='softmax')

# Tell tflearn how we want to train the network
network = regression(network, optimizer='adam',
loss='categorical_crossentropy',
learning_rate=0.001)

# Wrap the network in a model object
model = tflearn.DNN(network, tensorboard_verbose=0, checkpoint_path='bird-classifier.tfl.ckpt')

# Train it! We'll do 100 training passes and monitor it as it goes.
model.fit(X, Y, n_epoch=100, shuffle=True, validation_set=(X_test, Y_test),
show_metric=True, batch_size=96,
snapshot_epoch=True,
run_id='bird-classifier')

# Save model when training is complete to a file
model.save("bird-classifier.tfl")

如果你有一个好的图形显卡,有足够的RAM(比如NVIDIA的GeForce GTX 980 Ti或者更好的),训练工作耗时还不到一个小时。如果用普通的cpu训练,它可能需要更长的时间。

随着不停地训练,准确度将会增加。第一轮训练完成后,我得到75.4%的准确率。10轮训练后,准确度达到91.7%。50轮或者更多轮之后,准确度限制在95.5%。再多的训练已经没有帮助,所以我终止于此。

恭喜!我们的程序现在可以识别图片中的鸟类了!

测试我们神经网络

现在已经有一个训练过的神经网络,我们可以使用它。这里有一个简单的脚本,可以用来测试单张图像,判别其是否为鸟类。

但是真正去看我们的网络模型是如何有效地预测,我们需要测试大量的图片。我创建的数据集保留了15,000张图片,用于验证。当我在网络模型上运行这15,000张图片的时候,它预测的正确率有95%。

这似乎很好,对吗?嗯…这取决于!

真的有95%准确吗?

我们的网络模型声称95%准确。但是细节决定成败。这可以能意味着各种不同的东西。

举个例子,如果我们的训练集中有5%的鸟类图片和其他95%的非鸟类图片?一个猜”不是鸟“的程序每一次都是95%准确的!但它也是100%没有用的。

我们需要仔细的看这些数字而不是整体的准确率。要判断一个分类系统到底有多好,我们需要仔细看看它是如何失败的,而不仅仅是它失败次数的百分比。

之前考虑到我们的预测只有“正确”或者“错误”,现在我们打破这种常规,把我们的预测分为四个不同的类别——

  • 第一种,有一些鸟类图片,我们的神经网络可以正确地识别为鸟类。我们称之为真正例

Wow! Our network can recognize lots of different kinds of birds successfully!

  • 第二种,下面一些的图片,我们的神经网络正确地识别为“非鸟类”,称之为真反例

Horses and trucks don’t fool us!

  • 第三种,下面的一些图片,我们认为是鸟类,但不完全是真正的鸟。这些称之为假正例

Lots of planes were mistaken for birds! That makes sense.

  • 最后一种,下面的一些鸟类图片,我们的神经网络没有正确地识别为鸟类,称之为假反例

These birds fooled us! Stupid ostriches! Do they even count as birds?

对于15,000张验证图片,把每次的预测结果累加到不同的类别中:

enter description here

为什么要打破我们的结果而分成这样的四类?因为不是所有的错误都是等概率出现的。

想像一下,如果我们写一个程序来从核磁共振成像的图片中检测肿瘤。如果我们检测到肿瘤,我们宁愿相信这是假正例而不是假反例。假反例会产生更糟糕的情况——那就是当程序告诉某人没有得肿瘤,但实际上却患有肿瘤。

不只是粗略的观察整体的精确度,我们计算准确率和召回率指标。这两个指标给了我们一个在性能上更加清晰的画面:

enter description here

这张图告诉我们,97%的次数预测为“鸟类”,我们是正确的!但是也告诉我们,在数据集中我们只发现有90%是真正的鸟类图片。换句话说,我们可能找不到每一种鸟,但是可以确保找到它的时候一定是鸟类!

接下来做些什么

现在你已经知道深度卷积神经网络的基本知识,你可以尝试弄一弄tflearn的例程。它甚至有内置的数据集,所以你甚至不需要找你自己的图片。

热评文章